
///
/// This is the implementation of the Server class
///

#include <iostream>
#include <netdb.h>
#include <arpa/inet.h>
#include <cstring>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstdio>
#include "common.h"
#include "Handler.h"
#include <map>
#include <cassert>
#include "Server.h"
#include <algorithm>

Server* Server::mServer = NULL;

int Server::Initialize(const char *ip, const char *portNo)
{
	struct addrinfo temp, *res;
	memset(&temp, 0, sizeof(temp));		
	temp.ai_family = AF_UNSPEC;		
	temp.ai_socktype = SOCKET_TYPE;
	
	int retVal;

	if((retVal = getaddrinfo(ip, portNo, &temp, &res)) != 0)
	{
		std::cout << "getaddrinfo() error: " << gai_strerror(retVal) << std::endl;
		exit(1);
	}


	std::cout << "Starting the server..." << std::endl;
	std::cout << "IP Address: " << ip << std::endl;
	std::cout << "Port Number: " << portNo << std::endl;


	//create server main(connection request handler) socket
	mMainFD = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	if(mMainFD == -1)
	{
		perror("socket() error: ");
		exit(2);
	}

	//need to reuse the socket
	int yes = 1;
	if(setsockopt(mMainFD, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
	{
		perror("setsockopt() error: ");
		exit(2);
	}

	if(bind(mMainFD, res->ai_addr, res->ai_addrlen) == -1)
	{
		perror("bind() error: ");
		close(mMainFD);
		exit(2);
	}

	freeaddrinfo(res);

	if(listen(mMainFD, BACKLOG) == -1)
	{
		perror("listen() error:");
		close(mMainFD);
		exit(2);
	}

	std::cout << "Server started successfully!" << std::endl;

	struct sockaddr_in tempaddr;
	unsigned int templen = sizeof(struct sockaddr);
	getsockname(mMainFD, (struct sockaddr*)&tempaddr, &templen);
	std::cout << "Server listens to port: " << ntohs(tempaddr.sin_port) << std::endl;
	
	return 0;
}

int Server::Execute()
{

	struct sockaddr_storage clientAddr;
	socklen_t clientAddrSize = sizeof(clientAddr);

	//initialize FD sets for select()
	fd_set readFDS;
	FD_ZERO(&readFDS);
	FD_ZERO(&mConnectedFDS);
	
	FD_SET(mMainFD, &mConnectedFDS);
	FD_SET(STDIN, &mConnectedFDS); //for command line control of server
	mMaxFD = std::max(STDIN, mMainFD);

	struct timeval tv;
	tv.tv_sec = 300;	tv.tv_usec = 0; // 5 min timeout

	int retVal;

	SocketToHandlerMapping::iterator sockHandlerIter;
	OriginToClientFDMapping::iterator originToClientFDIter;

	while(1)
	{
		readFDS = mConnectedFDS;
		retVal = select(mMaxFD+1, &readFDS, NULL, NULL, &tv);
		if(retVal == -1)
		{
			perror("select() error: ");
			return 3;
		}
		else if(retVal == 0)
		{
			std::cout << "Timeout occured. Nobody's interested in chatting! Exiting..." << std::endl;
			return 0;
		}

		for(int i = 0; i <= mMaxFD; ++i)
		{
			if(FD_ISSET(i, &readFDS))
			{
				if(i == STDIN)
				{
					//TODO: handle std input commands
				}
				else if(i == mMainFD)
				{
					//handle request from unknown client

					int clientFD = accept(mMainFD, (struct sockaddr *)&clientAddr, &clientAddrSize);
					if(clientFD == -1)
					{
						perror("accept() error: ");
						close(mMainFD);
						return 3;
					}

					std::cout << "Accepted connection from a client!" << std::endl;

					//add socket to map
					Handler newConnHandler(clientFD, PROXY);
					mClientMap[clientFD] = newConnHandler;
					
					FD_SET(clientFD, &mConnectedFDS);
					if(clientFD > mMaxFD)
						mMaxFD = clientFD;
				}
				else
				{
					assert((mClientMap.find(i) != mClientMap.end()) ||
				     (mOriginToClientMap.find(i) 
					 	!= mOriginToClientMap.end())); //TODO: remove

					//handle request from known client
					int byteCount = recv(i, mMsgBuf, MSG_BUF_SIZE, 0);
					if(byteCount == -1)
					{
						perror("recv() error: ");

						KillClient(i);
					}
					else if(byteCount == 0)
					{
						std::cout << "Client closed the socket!" << std::endl;

						KillClient(i);
					}
					else
					{
						if(mClientMap.find(i) != mClientMap.end())
						{
							if(mClientMap[i].HandleMessage
								(mMsgBuf, byteCount) == -1) 
							{
								std::cout << "Server couldn't interpret the message" << std::endl;
								continue;							
							}
						}
						else
						{
							if(mClientMap[mOriginToClientMap[i]].HandleMessage(mMsgBuf, byteCount) == -1)
							{
								std::cout << "Server couldn't interpret the message" << std::endl;
								continue;
							}
						}
						
					}
				}
			}
		}
	}
	
	close(mMainFD);
	return 0;
}

int Server::MapOriginToClientFD(int originFD, int clientFD)
{
	assert(mClientMap.find(clientFD) != mClientMap.end()); //TODO: remove

	// add mapping for handler access
	mOriginToClientMap[originFD] = clientFD;
	
	// add to fd_set to be able to listen
	FD_SET(originFD, &mConnectedFDS);
	if(originFD > mMaxFD)
		mMaxFD = originFD;
	
	return 0;
}

int Server::Die(int clientFD, int originFD)
{
	close(clientFD);
	close(originFD);

	FD_CLR(clientFD, &mConnectedFDS);
	FD_CLR(originFD, &mConnectedFDS);
	
	if((mMaxFD == clientFD) || (mMaxFD == originFD))
		UpdateMaxFd();

	mClientMap.erase(clientFD); //this will delete the Handler
	mOriginToClientMap.erase(originFD);
	return 0;
}

//////////////////////
//// PRIVATE /////////
//////////////////////

void Server::KillClient(int sockFD)
{
	//close the socket
	close(sockFD);

	//remove socket from fd set
	FD_CLR(sockFD, &mConnectedFDS);
	if(sockFD == mMaxFD)
	{
		UpdateMaxFd();
	}

	//remove socket from map
	mClientMap.erase(sockFD);
}

void Server::UpdateMaxFd()
{
	for(int i = mMaxFD-1; i > 0; --i)
	{
		if(FD_ISSET(i, &mConnectedFDS))
		{
			mMaxFD = i;
			return;
		}
	}
}
